﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Run.cs" company="melcoprog">
//   Copyright (c) melcoprog. All rights reserved.
// </copyright>
// <summary>
//   Defines the Run type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace CreatureSheep.CreatureStates.Run
{
    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;

    using Creature;

    /// <summary>
    /// The run state.
    /// </summary>
    public class Run : ICreatureState
    {
        /// <summary>
        /// The gravity.
        /// </summary>
        private const double Gravity = 0.11;

        /// <summary>
        /// The run configuration.
        /// </summary>
        private readonly StateConfiguration configuration;

        /// <summary>
        /// The border.
        /// </summary>
        private readonly IBorder border;

        /// <summary>
        /// The frame count.
        /// </summary>
        private int frameCount;

        /// <summary>
        /// The pic.
        /// </summary>
        private double pic;

        /// <summary>
        /// The crash x.
        /// </summary>
        private bool crashx;

        /// <summary>
        /// The crash y.
        /// </summary>
        private bool crashy;

        /// <summary>
        /// Initializes a new instance of the <see cref="Run"/> class.
        /// </summary>
        /// <param name="runRepository">
        /// The run repository.
        /// </param>
        /// <param name="border">
        /// The border.
        /// </param>
        public Run(IStateRepository runRepository, IBorder border)
        {
            this.border = border;
            this.configuration = runRepository.GetConfiguration();
        }

        /// <summary>
        /// The make step.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// The <see cref="CreatureStep"/>.
        /// </returns>
        public CreatureStep MakeStep(Creature context)
        {
            var step = this.Move(context);

            if (this.frameCount > 100 * 2)
            {
                this.frameCount = 0;
                context.SetState();
            }

            this.frameCount++;
            return step;
        }

        #region

        /// <summary>
        /// The move single.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="dx">
        /// The delta x.
        /// </param>
        /// <param name="dy">
        /// The delta y.
        /// </param>
        private void MoveSingle(Creature context, double dx, double dy)
        {
            if ((!this.crashx) && (!this.border.StepIsBlocked(context, dx, 0, (int)this.configuration.Width, (int)this.configuration.Height)))
            {
                context.X = context.X + dx;
            }
            else
            {
                this.crashx = true;
            }

            if ((!this.crashy) && (!this.border.StepIsBlocked(context, 0, dy, (int)this.configuration.Width, (int)this.configuration.Height)))
            {
                context.Y = context.Y + dy;
            }
            else
            {
                this.crashy = true;
            }
        }

        /// <summary>
        /// The move.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="vx">
        /// The velocity x.
        /// </param>
        /// <param name="vy">
        /// The velocity y.
        /// </param>
        private void Move(Creature context, double vx, double vy)
        {
            var l = Math.Abs(Math.Abs(vx) > Math.Abs(vy) ? vx : vy);

            this.crashx = false;
            this.crashy = false;

            if (l > 0)
            {
                var dx = vx / l;
                var dy = vy / l;
                int i;
                for (i = 1; i <= (int)l; i++)
                {
                    this.MoveSingle(context, dx, dy);
                }

                var frac = l - (int)l;
                this.MoveSingle(context, dx * frac, dy * frac);
            }
        }

        /// <summary>
        /// The random.
        /// </summary>
        /// <param name="num">
        /// The number.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        private int Random(int num)
        {
            var rnd = new Random((int)(DateTime.Now.Ticks / 100));
            return rnd.Next(0, num);
        }

        /// <summary>
        /// The move.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// The <see cref="CreatureStep"/>.
        /// </returns>
        private CreatureStep Move(Creature context)
        {
            context.AccelerationY = Gravity;

            context.VelocityX = (context.VelocityX * 0.98) + context.AccelerationX;
            context.VelocityY = (context.VelocityY * 0.98) + context.AccelerationY;

            this.Move(context, context.VelocityX, context.VelocityY);

            if (this.crashx)
            {
                context.AccelerationX = -context.AccelerationX;
                context.VelocityX = -context.VelocityX;

                if (context.VelocityX > 0)
                {
                    context.Direction = Direction.Right;
                }

                if (context.VelocityX < 0)
                {
                    context.Direction = Direction.Left;
                }
            }

            if (this.crashy)
            {
                context.AccelerationY = -context.AccelerationY;
                context.VelocityY = -context.VelocityY;
            }

            context.AccelerationX = 0;

            switch (context.Direction)
            {
                case Direction.Right:
                    context.AccelerationX = 0.05;
                    break;
                case Direction.Left:
                    context.AccelerationX = -0.05;
                    break;
                case Direction.Down:
                    context.AccelerationX = 0;
                    break;
            }

            if (this.crashy)
            {
                if (this.Random(10) <= 1)
                {
                    context.VelocityY = -4 - this.Random(40);
                }
                else
                    if (this.Random(10) >= 9)
                    {
                        context.Direction = (context.Direction == Direction.Right) ? Direction.Left : Direction.Right;
                    }
            }

            context.Speed = Math.Sqrt((context.VelocityX * context.VelocityX) + (context.VelocityY * context.VelocityY));

            Bitmap image;
            GraphicsPath path;

            if (context.Direction == Direction.Left)
            {
                image = this.configuration.LeftSprites[(int)this.pic].Key;
                path = this.configuration.LeftSprites[(int)this.pic].Value;
            }
            else
            {
                image = this.configuration.RightSprites[(int)this.pic].Key;
                path = this.configuration.RightSprites[(int)this.pic].Value;
            }

            var step = new CreatureStep(image, context.X, context.Y, path);

            this.pic = this.pic + 0.1;
            if (this.pic > 2)
            {
                this.pic = 0;
            }

            return step;
        }

        #endregion
    }
}
